﻿using System;
using System.Drawing;
using System.Windows.Forms;

using Storm.TextEditor.Win32;
using Storm.TextEditor.Win32.Enums;
using Storm.TextEditor.Win32.Structs;

namespace Storm.TextEditor.Drawing.GDI
{
    public class GDISurface
        : GDIObject
    {
        #region Fields

        protected int width = 0;
        protected int height = 0;
        protected int tabSize = 4;

        protected IntPtr oldFont = IntPtr.Zero;
        protected IntPtr oldPen = IntPtr.Zero;
        protected IntPtr oldBrush = IntPtr.Zero;
        protected IntPtr oldBmp = IntPtr.Zero;

        protected IntPtr hDC = IntPtr.Zero;
        protected IntPtr hBMP = IntPtr.Zero;

        private WeakReference control = null;

        #endregion

        #region Properties

        private Control Control
        {
            get
            {
                if (control != null)
                    return (Control)control.Target;
                else
                    return null;
            }
            set { control = new WeakReference(value); }
        }

        public GDIFont Font
        {
            get
            {
                GDITextMetric tm = new GDITextMetric();
                string fontname = "                                                ";

                NativeGdi32Api.GetTextMetrics(hDC, ref tm);
                NativeGdi32Api.GetTextFace(hDC, 79, fontname);

                GDIFont gf = new GDIFont();
                gf.fontname = fontname;
                gf.bold = (tm.tmWeight > 400);
                gf.italic = (tm.tmItalic != 0);
                gf.underline = (tm.tmUnderlined != 0);
                gf.strikethrough = (tm.tmStruckOut != 0);

                gf.size = (int)(((double)(tm.tmMemoryHeight) / (double)tm.tmDigitizedAspectY) * 72);
                return gf;
            }
            set
            {
                IntPtr res = NativeGdi32Api.SelectObject(hDC, value.hFont);
                if (oldFont == IntPtr.Zero)
                    oldFont = res;
            }
        }

        public IntPtr HDC
        {
            get { return hDC; }
        }

        public IntPtr HBMP
        {
            get { return hBMP; }
        }

        public Color TextForeColor
        {
            get { return NativeUser32Api.IntToColor(NativeGdi32Api.GetTextColor(hDC)); }
            set { NativeGdi32Api.SetTextColor(hDC, NativeUser32Api.ColorToInt(value)); }
        }

        public Color TextBackColor
        {
            get { return NativeUser32Api.IntToColor(NativeGdi32Api.GetBkColor(hDC)); }
            set { NativeGdi32Api.SetBkColor(hDC, NativeUser32Api.ColorToInt(value)); }
        }

        public bool FontTransparent
        {
            get { return NativeGdi32Api.GetBkMode(hDC) < 2; }
            set { NativeGdi32Api.SetBkMode(hDC, value ? 1 : 2); }
        }

        #endregion

        #region Methods

        #region Protected

        protected void Init(int width, int height, IntPtr hdc)
        {
            this.width = width;
            this.height = height;

            hDC = NativeGdi32Api.CreateCompatibleDC(hdc);
            hBMP = NativeGdi32Api.CreateCompatibleBitmap(hdc, width, height);

            IntPtr ret = NativeGdi32Api.SelectObject(hDC, hBMP);
            oldBmp = ret;

            if (hDC == (IntPtr)0)
                throw new Exception("Creation of hDC failed.");

            if (hDC == (IntPtr)0)
                throw new Exception("Creation of hBMP failed.");
        }

        protected override void Destroy()
        {
            if (oldBmp != IntPtr.Zero)
                NativeGdi32Api.SelectObject(hDC, oldBmp);

            if (oldFont != IntPtr.Zero)
                NativeGdi32Api.SelectObject(hDC, oldFont);

            if (oldPen != IntPtr.Zero)
                NativeGdi32Api.SelectObject(hDC, oldPen);

            if (oldBrush != IntPtr.Zero)
                NativeGdi32Api.SelectObject(hDC, oldBrush);

            if (hBMP != (IntPtr)0)
                NativeGdi32Api.DeleteObject(hBMP);

            if (hDC != (IntPtr)0)
                NativeGdi32Api.DeleteDC(hDC);

            hBMP = (IntPtr)0;
            hDC = (IntPtr)0;

            base.Destroy();
        }

        #endregion

        #region Public

        public Size MeasureTabbedString(string text, int tabSize)
        {
            uint ret = NativeUser32Api.GetTabbedTextExtent(hDC, text, text.Length, 1, ref tabSize);
            return new Size((int)(ret & 0xFFFF), (int)((ret >> 16) & 0xFFFF));
        }

        public Size DrawTabbedString(string text, int x, int y, int tabOrigin, int tabSize)
        {
            int ret = NativeUser32Api.TabbedTextOut(hDC, x, y, text, text.Length, 1, ref tabSize, tabOrigin);
            return new Size(ret & 0xFFFF, (ret >> 16) & 0xFFFF);
        }

        public void RenderTo(IntPtr hdc, int x, int y)
        {
            NativeGdi32Api.BitBlt(hdc, x, y, width, height, hDC, 0, 0, (int)GDIRop.SrcCopy);
        }

        public void RenderTo(GDISurface target, int x, int y)
        {
            this.RenderTo(target.hDC, x, y);
        }

        public void RenderTo(GDISurface target, int sourceX, int sourceY, int width, int height, int destX, int destY)
        {
            NativeGdi32Api.BitBlt(target.hDC, destX, destY, width, height, hDC, sourceX, sourceY, (int)GDIRop.SrcCopy);
        }

        public void RenderToControl(int x, int y)
        {
            IntPtr hdc = NativeUser32Api.ControlDC(this.Control);

            this.RenderTo(hdc, x, y);
            NativeUser32Api.ReleaseDC(this.Control.Handle, hdc);
        }

        public Graphics CreateGraphics()
        {
            return Graphics.FromHdc(hDC);
        }

        public void FillRect(GDIBrush brush, int x, int y, int width, int height)
        {
            RECTAPI gr;
            gr.Top = y;
            gr.Left = x;
            gr.Right = width + x;
            gr.Bottom = height + y;

            NativeUser32Api.FillRect(hDC, ref gr, brush.brush);
        }

        public void DrawFocusRect(int x, int y, int width, int height)
        {
            RECTAPI gr;
            gr.Top = y;
            gr.Left = x;
            gr.Right = width + x;
            gr.Bottom = height + y;

            NativeUser32Api.DrawFocusRect(hDC, ref gr);
        }

        public void FillRect(Color color, int x, int y, int width, int height)
        {
            GDIBrush b = new GDIBrush(color);
            this.FillRect(b, x, y, width, height);
            b.Dispose();
        }

        public void InvertRect(int x, int y, int width, int height)
        {
            RECTAPI gr;
            gr.Top = y;
            gr.Left = x;
            gr.Right = width + x;
            gr.Bottom = height + y;

            NativeUser32Api.InvertRect(hDC, ref gr);
        }

        public void DrawLine(GDIPen pen, Point p1, Point p2)
        {
            IntPtr oldpen = NativeGdi32Api.SelectObject(hDC, pen.hPen);

            POINTAPI gp;
            gp.X = 0;
            gp.Y = 0;

            NativeGdi32Api.MoveToEx(hDC, p1.X, p1.Y, ref gp);
            NativeGdi32Api.LineTo(hDC, p2.X, p2.Y);

            IntPtr crap = NativeGdi32Api.SelectObject(hDC, oldpen);
        }

        public void DrawLine(Color color, Point p1, Point p2)
        {
            GDIPen p = new GDIPen(color, 1);
            this.DrawLine(p, p1, p2);

            p.Dispose();
        }

        public void DrawRect(Color color, int left, int top, int width, int height)
        {
            GDIPen p = new GDIPen(color, 1);
            this.DrawRect(p, left, top, width, height);

            p.Dispose();
        }

        public void DrawRect(GDIPen pen, int left, int top, int width, int height)
        {
            this.DrawLine(pen, new Point(left, top), new Point(left + width, top));
            this.DrawLine(pen, new Point(left, top + height), new Point(left + width, top + height));
            this.DrawLine(pen, new Point(left, top), new Point(left, top + height));
            this.DrawLine(pen, new Point(left + width, top), new Point(left + width, top + height + 1));
        }

        public void Clear(Color color)
        {
            GDIBrush b = new GDIBrush(color);
            this.Clear(b);

            b.Dispose();
        }

        public void Clear(GDIBrush brush)
        {
            this.FillRect(brush, 0, 0, width, height);
        }

        public void Flush()
        {
            NativeGdi32Api.GdiFlush();
        }

        public void SetBrushOrg(int x, int y)
        {
            POINTAPI p;
            p.X = 0;
            p.Y = 0;

            NativeGdi32Api.SetBrushOrgEx(hDC, x, y, ref p);
        }

        #endregion

        #endregion

        /// <summary>
        /// Initializes a new instance of GDISurface.
        /// </summary>
        public GDISurface(IntPtr hDC)
        {
            this.hDC = hDC;
        }

        /// <summary>
        /// Initializes a new instance of GDISurface.
        /// </summary>
        public GDISurface(int width, int height)
        {
            IntPtr deskDC = NativeUser32Api.GetDC(new IntPtr(0));
            this.Init(width, height, deskDC);
            this.Create();
        }

        /// <summary>
        /// Initializes a new instance of GDISurface.
        /// </summary>
        public GDISurface(int width, int height, IntPtr hdc)
        {
            this.Init(width, height, hdc);
            this.Create();
        }

        /// <summary>
        /// Initializes a new instance of GDISurface.
        /// </summary>
        public GDISurface(int width, int height, GDISurface surface)
        {
            this.Init(width, height, surface.hDC);
            this.Create();
        }

        /// <summary>
        /// Initializes a new instance of GDISurface.
        /// </summary>
        public GDISurface(int width, int height, Control compatibleControl, bool bindControl)
        {
            IntPtr hDCControk = NativeUser32Api.ControlDC(compatibleControl);
            this.Init(width, height, hDCControk);
            NativeUser32Api.ReleaseDC(compatibleControl.Handle, hDCControk);

            if (bindControl)
                this.Control = compatibleControl;

            this.Create();
        }
    }
}
